﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using KC.Framework.Util;
using KC.Framework.Base;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using System.Threading.Tasks;

namespace KC.Storage.BlobService
{
    internal partial class AzureReader : ReaderBase
    {
        private readonly CloudStorageAccount Account;
        private readonly CloudBlobClient Client;
        private readonly AzureCache Cache;

        public AzureReader(string connectionString, AzureCache cache)
            : base()
        {
            if (string.IsNullOrWhiteSpace(connectionString))
                throw new ArgumentException("Azure Storage's connect string is empy or null.", "connectionString");

            this.Account = CloudStorageAccount.Parse(connectionString);
            this.Client = this.Account.CreateCloudBlobClient();
            this.Cache = cache;
        }

        private CloudBlob GetBlob(string containerName, string blobId, bool isBlockBlob = false)
        {
            if (string.IsNullOrWhiteSpace(containerName))
            {
                throw new ArgumentNullException("ContainerName is empty or null in KC.Storage.BlobService.AzureReader", "containerName");
                //return isBlockBlob ? this.Client.GetBlockBlobReference(blobId) : this.Client.GetBlobReference(blobId);
            }
            else
            {
                CloudBlobContainer container = this.Client.GetContainerReference(containerName);
                return isBlockBlob ? container.GetBlockBlobReference(blobId) : container.GetBlobReference(blobId);
            }
        }

        private BlobInfo GetBlob(string tenant, string encryptionKey, bool isInternal, string userId, string containerName, string blobId, bool generateCacheData, out byte[] cacheData, out byte[] cacheMetadata)
        {
            cacheData = null;
            cacheMetadata = null;

            if (string.IsNullOrEmpty(blobId))
                return null;

            CloudBlob blob = GetBlob(containerName, blobId);
            if (blob == null)
                return null;

            Dictionary<string, string> blobMetadata = GetBlobMetadata(blob).Result;
            CheckBlobAccess(tenant, isInternal, userId, blobId, blobMetadata);

            var fileByteLength = blob.Properties.Length;
            var data = new byte[fileByteLength];

            var success = DownloadByteArray(blob, data).Result;
            if (!success) return null;

            // Store Cache Data
            if (generateCacheData)
            {
                cacheData = new byte[data.Length];
                Buffer.BlockCopy(data, 0, cacheData, 0, data.Length);

                string metadataXml = MetadataToXml(blobMetadata);
                cacheMetadata = Encoding.UTF8.GetBytes(metadataXml);
            }
            else
            {
                cacheData = null;
                cacheMetadata = null;
            }

            string type = blobMetadata.ContainsKey("Type") ? blobMetadata["Type"] : "Unknown";
            string format = blobMetadata.ContainsKey("Format") ? blobMetadata["Format"] : "Unknown";
            string fileName = blobMetadata.ContainsKey("FileName") ? blobMetadata["FileName"] : "Unknown";
            byte[] blobActualData = DecryptIfNeeded(encryptionKey, isInternal, userId, data, blobMetadata);
            BlobInfo bi = new BlobInfo(blobId, type, format, fileName, blobActualData);
            bi.Size = bi.Data.Length;
            return bi;
        }

        private async Task<bool> DownloadByteArray(CloudBlob blob, byte[] data)
        {
            try
            {
                int i = 0;
                do
                {
                    i++;
                    var result = await blob.DownloadToByteArrayAsync(data, 0);
                    if (result > 0)
                        return true;
                } while (i > 3);
                
                return true;
            }
            catch (Exception ex)
            {
                LogUtil.LogError(string.Format(
                    "----blob.DownloadByteArray throw error: {0}",
                    ex.Message));
                return false;
            }
        }

        private async Task<Dictionary<string, string>> GetBlobMetadata(CloudBlob blob)
        {
            try
            {
                int i = 0;
                do
                {
                    i++;
                    await blob.FetchAttributesAsync();
                    if (blob.Metadata.Any())
                        break;
                } while (i > 3);
            }
            catch (Exception ex)
            {
                LogUtil.LogError(string.Format(
                    "----blob.GetBlobMetadata throw error: {0}",
                    ex.Message));
            }

            Dictionary<string, string> metadataDict = new Dictionary<string, string>();
            foreach (var key in blob.Metadata.Keys)
            {
                metadataDict.Add(key, blob.Metadata[key]);
            }
            return metadataDict;
        }
    }
}